﻿using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;

namespace RemoteObject
{

    public class UploadWorker : FileTransWorker
    {
        public RemoteObject.RemoteObjectUploader proxyObjectTcpReuse = null;

        private int remainDataLengthToRead;
        private int remainDataLengthToSend;

        private Common.BlockBufferInfo blockToRead = null;
        private Common.BlockBufferInfo blockToSend = null;

        public UploadWorker(String _fnRemote, String _fnLocal, String _serverUrl, BlockInfo _blockInfo, UploadJob _job)
        {
            fnRemote = _fnRemote;
            fnLocal = _fnLocal;
            blockInfo = _blockInfo;
            serverUrl = _serverUrl;
            ownerJob = _job;
            ownerJob.fileTransWorkers.Add(this);
        }

        protected override void _FileAccessProc()
        {
            do
            {
                if (remainDataLengthToRead <= 0 || stopFlag)
                    break;

                //wait GetNextBlock's notification for next block reading.
                double timeout_in_minutes = 15;
                if (false == nextfileAccessEvent.WaitOne((int)(1000 * 60 * timeout_in_minutes)))
                {
                    //no response for nMinutes, then kill this client
                    break;
                }

                if (remainDataLengthToRead <= 0 || stopFlag)
                    break;

                //start read file block
                {
                    Common.BlockBufferInfo bbi = blockToRead;

                    //read new file block
                    bbi.dataSize = Math.Min(remainDataLengthToRead, Math.Min(param.BlockSize, Common.BlockSize));
                    int readLength = 0;
                    try
                    {
                        if (localFileStream == null)
                        {
                            localFileStream = new FileStream(fnLocal, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                            localFileStream.Seek(blockInfo.currentPos, SeekOrigin.Begin);
                        }
                        readLength = localFileStream.Read(bbi.buffer, bbi.bufferStartPos, bbi.dataSize);
                    }
                    catch
                    {
                        accessFileError = true;
                        break;
                    }
                    
                    if (readLength != bbi.dataSize)
                    {
                        //read file error
                        accessFileError = true;
                        break;
                    }

                    bbi.dataCompressed = false;
                    bbi.dataSizeUncompressed = bbi.dataSize;

                    if (ownerJob.useCompressionMode)
                    {
                        //compute the compress rate
                        Byte[] compressedBlock = MyUtility.CompressionRange(bbi.buffer, bbi.bufferStartPos, bbi.dataSize);
                        if (1.0 * compressedBlock.Length / bbi.dataSize > 0.95)
                        {
                            //ignore the compressed data if compression rate is too low.
                        }
                        else
                        {
                            bbi.dataSize = compressedBlock.Length;
                            bbi.dataCompressed = true;
                            Buffer.BlockCopy(compressedBlock, 0, bbi.buffer, bbi.bufferStartPos, compressedBlock.Length);
                        }
                    }

                    remainDataLengthToRead -= readLength;

                    bbi.isBufferEmpty = false;
                }

            }while(true);

        }

        Common.BlockBufferInfo ReadNextBlockFromFileOnWait()
        {
            while(blockToRead.isBufferEmpty)
            {
                Thread.Sleep(1);
                if (accessFileError||stopFlag)
                    return null;
            }

            Common.BlockBufferInfo nextBlock = null;
            if ( !accessFileError )
            {
                if (blockToRead.dataSize == Common.BlockSize)
                {
                    //full block then use global blockbuffer.
                    Buffer.BlockCopy(blockToRead.buffer, blockToRead.bufferStartPos,
                                     blockToSend.buffer, blockToSend.bufferStartPos, blockToRead.dataSize);
                    blockToSend.CloneInfoFrom(blockToRead);
                    nextBlock = blockToSend;
                }
                else
                {
                    Common.BlockBufferInfo newBlock = new Common.BlockBufferInfo();
                    newBlock.buffer = new Byte[blockToRead.dataSize];
                    newBlock.CloneInfoFrom(blockToRead);
                    Buffer.BlockCopy(blockToRead.buffer, blockToRead.bufferStartPos,
                                     newBlock.buffer, newBlock.bufferStartPos, blockToRead.dataSize);
                    nextBlock = newBlock;
                }
            }

            blockToRead.isBufferEmpty = true;
            nextfileAccessEvent.Set(); // start to read next block.

            return nextBlock;
        }

        public RemoteObject.RemoteObjectUploader GetRemoteObject()
        {
            return (RemoteObject.RemoteObjectUploader)Activator.GetObject(typeof(RemoteObject.RemoteObjectUploader), serverUrl);
        }

        public override void Run()
        {
            base.Run();
            int remainDataLengthOld = remainDataLengthToRead = remainDataLengthToSend = blockInfo.remainDataLength;
            int currentPosOld = blockInfo.currentPos;

            //set transfer param
            param.BlockSize = Common.BlockSize;
            param.UseCompressMode = ownerJob.useCompressionMode;

            String debugInfo = "("+ blockInfo.blockID + ") " + blockInfo.originPos.ToString("D10") + "-" + (blockInfo.currentPos + blockInfo.remainDataLength).ToString("D10") + " From " + serverUrl.Substring(6, 20);
            try
            {
                Console.WriteLine(debugInfo);

                RemoteObject.RemoteObjectUploader proxyObjectTcp = null;
                long clientID = 0;

                //try to init file transfer
                try
                {
                    proxyObjectTcp = proxyObjectTcpReuse != null ? proxyObjectTcpReuse : GetRemoteObject();
                    clientID = proxyObjectTcp.GetNewClientID();
                    proxyObjectTcp.StartUploadFile(fnRemote, clientID, blockInfo.currentPos, remainDataLengthToRead, param);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Network problem ..... ");
                    Console.WriteLine(e);
                    proxyObjectTcp = null;
                }

                proxyObjectTcpReuse = null;

                if (proxyObjectTcp != null)
                {
                    blockToSend = Common.getFreeBlockOnWait(0);
                    blockToRead = Common.getFreeBlockOnWait(0);

                    // start the file access thread
                    Thread thread = new Thread(new ThreadStart(FileAccessProc));
                    thread.Start();

                    try
                    {
                        int remoteRemainDataLengthToSave = 0;
                        while( remainDataLengthToSend > 0 && !stopFlag  )
                        {
                            Common.BlockBufferInfo bbi = ReadNextBlockFromFileOnWait();
                            if (bbi == null)
                                break;

                            remoteRemainDataLengthToSave = proxyObjectTcp.SendingFileData(clientID, bbi.dataCompressed, bbi.buffer);
                            if( remoteRemainDataLengthToSave < 0 )
                                break; // sending file failed.

                            //after sending successful, then update the block info. This is important for broken-resuming
                            remainDataLengthToSend -= bbi.dataSizeUncompressed;
                            lock (ownerJob)
                            {
                                blockInfo.remainDataLength = remoteRemainDataLengthToSave;
                                blockInfo.currentPos = currentPosOld + (remainDataLengthOld - remoteRemainDataLengthToSave);
                                //update speed data
                                ownerJob.transferredDataLength += bbi.dataSize;
                                transferredDataLength += bbi.dataSize;
                            }
                            ownerJob.UpdateProgressFile();
                        }
                        remoteRemainDataLengthToSave = proxyObjectTcp.StopUploadFile(clientID);
                        lock (ownerJob)
                        {
                            blockInfo.remainDataLength = remoteRemainDataLengthToSave;
                            blockInfo.currentPos = currentPosOld + (remainDataLengthOld - remoteRemainDataLengthToSave);
                        }
                        ownerJob.UpdateProgressFile();
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(debugInfo + " ---- error1 --- ");
                        Console.WriteLine(e);
                    }
                    finally
                    {
                        proxyObjectTcp = null;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(debugInfo + " ---- error2 --- ");
                Console.WriteLine(e);
            }
            finally
            {
                TimeSpan ts = DateTime.Now - startTime;
                Console.WriteLine(debugInfo + "-done- " + ts.TotalMilliseconds.ToString("f1") + " FreeBlocks : " + Common.getFreeBlockNum() + " Speed: " + this.GetTransferSpeed().ToString("f1") + "KB/s");

                WaitFileAccessProcToStop();

                Common.releaseBlock(blockToRead);
                Common.releaseBlock(blockToSend);
            }
        }

    }
}
